package org.budo.sharding.hint.mybatis;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.budo.sharding.hint.AnnotationMasterOnlySupport;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import io.shardingsphere.api.HintManager;

/**
 * @author limingwei
 * 
 * @see com.tj.plateform.dao.HintPlugin
 */
@Intercepts({ @Signature(type = Executor.class, //
        method = "query", //
        args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) //
})
public class AnnotationMasterOnlyMybatisPlugin extends AnnotationMasterOnlySupport implements Interceptor {
    private static final Map<String, Boolean> STATEMENT_MASTER_ONLY_CACHE = new ConcurrentHashMap<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        HintManager hintManager = null;

        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
            if (this.getDefaultMasterOnly() || this.annotatedMasterOnly(invocation)) {
                hintManager = HintManager.getInstance();
                hintManager.setMasterRouteOnly();
            }
        }

        try {
            return invocation.proceed();
        } finally {
            if (null != hintManager) {
                hintManager.close();
            }
        }
    }

    /**
     * 是否强制走主库
     */
    private boolean annotatedMasterOnly(Invocation invocation) throws ClassNotFoundException {
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0]; // 读方法

        if (!mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) {
            return false;
        }

        String statementId = mappedStatement.getId();
        Boolean masterOnly = STATEMENT_MASTER_ONLY_CACHE.get(statementId);
        if (null != masterOnly) { // 缓存
            return masterOnly;
        }

        masterOnly = this.annotatedMasterOnly(statementId);
        STATEMENT_MASTER_ONLY_CACHE.put(statementId, masterOnly);
        return masterOnly;
    }

    private Boolean annotatedMasterOnly(String statementId) throws ClassNotFoundException {
        int a = statementId.lastIndexOf(".");
        String className = statementId.substring(0, a);
        String methodName = statementId.substring(a + 1, statementId.length());

        Method[] methods = Class.forName(className).getMethods();

        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                if (this.annotatedMasterOnly(method)) {
                    return true;
                }
            }
        }

        return false;
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
